package com.gitee.ed.structure.stream;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.gitee.ed.common.CommonUtils;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * 封装 Stream 扩展更多简易接口
 *
 * @author hongda.li
 */
public final class StreamWrapper<Data> {

    private final Stream<Data> stream;

    public StreamWrapper(Stream<Data> stream) {
        this.stream = stream;
    }

    public static <Data> StreamWrapper<Data> of(final Stream<Data> stream) {
        return new StreamWrapper<>(stream);
    }

    public <Other> StreamWrapper<Other> convert(final Function<Data, Other> function) {
        return new StreamWrapper<>(stream.map(function));
    }

    public StreamWrapper<Data> distinct() {
        return new StreamWrapper<>(stream.distinct());
    }

    /**
     * 按照指定字段去重
     *
     * @param function 指定的字段
     * @param <Result> 指定字段的值类型
     * @return 链式调用
     */
    public <Result> StreamWrapper<Data> distinct(Function<Data, Result> function) {
        List<Data> list = this.stream.toList();
        return new StreamWrapper<>(CommonUtils.toLinkedHashMap(function, Function.identity(), list)
                .values()
                .stream());
    }

    public StreamWrapper<Data> whereNotNull() {
        return new StreamWrapper<>(this.stream.filter(Objects::nonNull));
    }

    public <Result> StreamWrapper<Data> whereNotNull(Function<Data, Result> function) {
        return this.where(function, Objects::nonNull);
    }

    public <Result> StreamWrapper<Data> whereIsNull(Function<Data, Result> function) {
        return this.where(function, Objects::isNull);
    }

    public <Result> StreamWrapper<Data> whereNotEmpty(Function<Data, Result> function) {
        return this.where(function, ObjectUtil::isNotEmpty);
    }

    public <Result> StreamWrapper<Data> whereIsEmpty(Function<Data, Result> function) {
        return this.where(function, ObjectUtil::isEmpty);
    }

    public <Result> StreamWrapper<Data> whereEqIfNotNull(Function<Data, Result> function, Data data) {
        if (ObjectUtil.isNull(data)) {
            return this;
        } else {
            return this.whereEquals(function, data);
        }
    }

    public <Result> StreamWrapper<Data> whereEqIfNotEmpty(Function<Data, Result> function, Data data) {
        if (ObjectUtil.isEmpty(data)) {
            return this;
        } else {
            return this.whereEquals(function, data);
        }
    }

    public <Result> StreamWrapper<Data> whereEquals(Function<Data, Result> function, Data data) {
        return this.where(function, result -> ObjectUtil.equals(result, data));
    }

    public StreamWrapper<Data> whereLike(Function<Data, String> function, String data) {
        return this.where(function, result -> CharSequenceUtil.startWith(result, data) || CharSequenceUtil.endWith(result, data));
    }

    public StreamWrapper<Data> whereStartWith(Function<Data, String> function, String data) {
        return this.where(function, result -> CharSequenceUtil.startWith(result, data));
    }

    public StreamWrapper<Data> whereEndWith(Function<Data, String> function, String data) {
        return this.where(function, result -> CharSequenceUtil.endWith(result, data));
    }

    public <Result> StreamWrapper<Data> whereIn(Function<Data, Result> function, Collection<Result> collection) {
        return this.where(function, result -> CollUtil.contains(collection, result));
    }

    public <Result> StreamWrapper<Data> whereNotIn(Function<Data, Result> function, Collection<Result> collection) {
        return this.where(function, result -> !CollUtil.contains(collection, result));
    }

    public <T, Result extends Comparable<T>> StreamWrapper<Data> whereGt(final Function<Data, Result> function, T min) {
        return this.where(function, result -> {
            Objects.requireNonNull(result);
            Objects.requireNonNull(min);
            return result.compareTo(min) > 0;
        });
    }

    public <T, Result extends Comparable<T>> StreamWrapper<Data> whereGte(final Function<Data, Result> function, T min) {
        return this.where(function, result -> {
            Objects.requireNonNull(result);
            Objects.requireNonNull(min);
            return result.compareTo(min) >= 0;
        });
    }

    public <T, Result extends Comparable<T>> StreamWrapper<Data> whereLt(final Function<Data, Result> function, T max) {
        return this.where(function, result -> {
            Objects.requireNonNull(result);
            Objects.requireNonNull(max);
            return result.compareTo(max) < 0;
        });
    }

    public <T, Result extends Comparable<T>> StreamWrapper<Data> whereLte(final Function<Data, Result> function, T max) {
        return this.where(function, result -> {
            Objects.requireNonNull(result);
            Objects.requireNonNull(max);
            return result.compareTo(max) <= 0;
        });
    }

    public <T, Result extends Comparable<T>> StreamWrapper<Data> whereBetween(Function<Data, Result> function, T min, T max) {
        return this.where(function, result -> {
            Objects.requireNonNull(result);
            Objects.requireNonNull(min);
            Objects.requireNonNull(max);
            return result.compareTo(min) >= 0 && result.compareTo(max) <= 0;
        });
    }

    public <Result> StreamWrapper<Data> where(Function<Data, Result> function, Predicate<Result> filter) {
        return new StreamWrapper<>(stream.filter(data -> data != null && filter.test(function.apply(data))));
    }

    public StreamWrapper<Data> sort() {
        return new StreamWrapper<>(this.stream.sorted());
    }

    public StreamWrapper<Data> sort(Comparator<Data> comparator) {
        return new StreamWrapper<>(this.stream.sorted(comparator));
    }

    public StreamWrapper<Data> sortDesc(Comparator<Data> comparator) {
        return new StreamWrapper<>(this.stream.sorted(comparator.reversed()));
    }

    @SuppressWarnings("unchecked")
    public <T, Result extends Comparable<T>> StreamWrapper<Data> sort(Function<Data, Result> function) {
        return new StreamWrapper<>(this.stream.sorted((o1, o2) -> function.apply(o1).compareTo((T) function.apply(o2))));
    }

    @SuppressWarnings("unchecked")
    public <T, Result extends Comparable<T>> StreamWrapper<Data> sortDesc(Function<Data, Result> function) {
        return new StreamWrapper<>(this.stream.sorted(((Comparator<Data>) (o1, o2) -> function.apply(o1).compareTo((T) function.apply(o2))).reversed()));
    }

    public <Other> StreamWrapper<JoinResult<Data, Other>> join(Stream<Other> otherStream, JoinCondition<Data, Other> joinCondition) {
        List<JoinResult<Data, Other>> results = new ArrayList<>();
        List<Other> others = otherStream.toList();
        this.stream.forEach(left -> others.forEach(right -> {
            if (joinCondition.onJoin(left, right)) {
                results.add(new JoinResult<>(left, right));
            }
        }));
        return new StreamWrapper<>(results.stream());
    }

    public <Other> StreamWrapper<JoinResult<Data, Other>> leftJoin(Stream<Other> otherStream, JoinCondition<Data, Other> joinCondition) {
        List<JoinResult<Data, Other>> results = new ArrayList<>();
        List<Other> others = otherStream.toList();
        this.stream.forEach(left -> others.forEach(right -> {
            if (joinCondition.onJoin(left, right)) {
                results.add(new JoinResult<>(left, right));
            } else {
                results.add(new JoinResult<>(left, null));
            }
        }));
        return new StreamWrapper<>(results.stream());
    }

    public <Other> StreamWrapper<JoinResult<Data, Other>> rightJoin(Stream<Other> otherStream, JoinCondition<Data, Other> joinCondition) {
        List<JoinResult<Data, Other>> results = new ArrayList<>();
        List<Other> others = otherStream.toList();
        this.stream.forEach(left -> others.forEach(right -> {
            if (joinCondition.onJoin(left, right)) {
                results.add(new JoinResult<>(left, right));
            } else {
                results.add(new JoinResult<>(null, right));
            }
        }));
        return new StreamWrapper<>(results.stream());
    }

    public Stream<Data> toStream() {
        return this.stream;
    }

    public StreamWrapper<Data> toStreamWrapper() {
        return new StreamWrapper<>(this.stream);
    }
}
